package jalgebrava.group.groups;

import static jalgebrava.group.groups.Groups.*;
import jalgebrava.group.Group;
import jalgebrava.util.Factorization;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * Utility class to return all groups of a given size.
 */
public final class AllGroups {
	private final static Logger logger = Logger.getLogger(AllGroups.class);
	@SuppressWarnings("unused")
	private final static AllGroups instance = new AllGroups();
	private AllGroups() {
		// empty, not covered
	}
	/**
	 * Return all groups of the given size, in no particular order.
	 * @param n
	 * @return a collection of groups of the given size
	 */
	public final static List<Group<?>> allGroupsForSize(int n) {
		List<Group<?>> result = new LinkedList<Group<?>>();
		Factorization fact = new Factorization(n);
		result.addAll(allAbelianGroupsForSize(fact));
		
		if (fact.getUniquePrimeFactors().contains(2) && n > 4) {
			result.add(dihedral(n));
		}
		if (n == 8) {
			result.add(quaternion(8));
		}
		return result;
	}
	public final static List<Group<?>> allAbelianGroupsForSize(int n) {
		return allAbelianGroupsForSize(new Factorization(n));
	}
	private final static int numberOfPartitions(int i) {
		switch(i) {
		case 1: return 1; // 1
		case 2: return 2; // 2, 11
		case 3: return 3; // 3, 21, 111
		case 4: return 5; // 4, 31, 22, 211, 1111
		//case 5: return 7; // 5, 41, 32, 311, 221, 2111, 11111
		default: throw new RuntimeException("A good partition function has not yet been implemented " + i);
		}
	}
	
	private final static int[][] getPartitions(int i) { 
		switch(i) {
		case 1: return new int[][] {{1}};
		case 2: return new int[][] {{2}, {1,1}};
		case 3: return new int[][] {{3} , {2,1}, {1,1,1}};
		case 4: return new int[][] {{4}, {3,1}, {2,2}, {2,1,1}, {1,1,1,1}};
		default: throw new RuntimeException("A good getPartitions function has not yet been implemented");
		}
	}
	private final static int pow(int p, int n) {
		int result = 1;
		for (int i=0;i<n;i++) {
			result *= p;
		}
		return result;
	}
	private final static Group<?> partitionToGroup(int p, int[] partition) {
		if (partition.length == 0) {
			throw new RuntimeException();			
		}
		Group<?> result = cyclic(pow(p, partition[0]));
		for (int i=1;i<partition.length;i++) {
			result = directProduct(result, cyclic(pow(p, partition[i])));
		}
		return result;
	}
	
	final static List<Group<?>> allAbelianGroupsForSize(Factorization f) {
		if (f.getUniquePrimeFactors().isEmpty()) {
			List<Group<?>> result = new LinkedList<Group<?>>();
			result.add(cyclic(1));
			return result;
		}
		int num = 1;
		
		Group<?>[][] gss;
		gss = new Group<?>[f.getUniquePrimeFactors().size()][];
		int factorCount = 0;
		for (int p: f.getUniquePrimeFactors()) {
			num *= numberOfPartitions(f.occuringPowerOf(p));
			gss[factorCount] = new Group<?>[numberOfPartitions(f.occuringPowerOf(p))];
			int count = 0;
			for (int[] partition: getPartitions(f.occuringPowerOf(p))) {
				Group<?> g = partitionToGroup(p, partition);
				logger.trace("Created " + g.getDescription());
				gss[factorCount][count] = g;
				count++;
			}
			factorCount++;
		}

		logger.trace("We expect " + num + " results");
		
		List<Group<?>> result = new ArrayList<Group<?>>(num);
		addAllDirectProducts(gss, result);
		return result;
	}
	private static final void addAllDirectProducts(Group<?>[][] gss, List<Group<?>> result) {
		if (gss.length == 0) {
			return;
		}
		if (gss.length == 1) {
			for (Group<?> g: gss[0]) {
				result.add(g);
			}
			return;
		}
		if (gss.length == 2) {
			for (Group<?> g: gss[0]) {
				for (Group<?> h: gss[1]) {
					result.add(directProduct(g, h));
				}
			}
			return;
		}
		throw new RuntimeException("Write a proper recursive algorithm!");
	}
}
